#ifndef SYMBOL_H_
#   include "symbol.h"
#endif

class   TSymbolTerminal : public TSymbolAtom
    {
    TTokenIndex     TokenIndex;
public:
    TSymbolTerminal(TTokenIndex Index);
    };

TSymbolTerminal::TSymbolTerminal(TTokenIndex Index)
    : TokenIndex(Index)
    {
    Flags   = F_TERMINAL;
    }

class   TSymbolNonTerminal : public TSymbolAtom
    {
public:
    TSymbolNonTerminal(TTokenIndex Index);
    };
class   TSymbolRule : public TSymbolAtom
    {
public:
    TSymbolRule(const string& Name);
    };

TSymbolRule::TSymbolRule(const string& Name)
    {
    Flags   = F_RULE;
    }


#if 0
/* AddSymbol() - add a symbol to a symbol.
 *
 * Every symbol has an associated vector of zero or more other symbols.
 *     nonterminal - vector of rule names
 *     rule        - vector of heterogenous production symbols
 *     terminal    - <empty>
 */
void TSymbolAtom::AddSymbol(TSymbol Symbol, TTokenIndex TokenId)
    {
    auto SymbolName = Symbol->Name();

    printf("Add symbol '%s' to '%s'\n",
        Symbol->Name_.c_str(), Name_.c_str());

    // either every symbol should be getting token IDs or none of them...
//    assert(Derives.size() == TokenIds.size() || TokenId < 0);
    Derives.push_back(Symbol);
//    if(TokenId >= 0)
//        TokenIds.push_back(TokenId);
    printf("    count(Derives) = %d\n", (int)Derives.size());
    }

#endif
#if 0
TSymbolInstance* TNonTerminal::AddToRhs(TSymbol* Symbol, TTokenIndex Index)
    {
    TRule*                      CurrentRule = Rules.back().get();
    unique_ptr<TSymbolInstance> Safe { new TSymbolInstance(Symbol, Index); }
    TSymbolInstance*            Result = Safe.get();

    
    return Result;
    }

#endif
TSymbolTable::~TSymbolTable() {}

/* FindAtom() - return raw pointer to atom if it exists.
 *
 */
TSymbolAtom*    TSymbolTable::FindAtom(string Name)
    {
    TSymbolAtom*    Result = nullptr;

    if(Atoms.count(Name))
        Result  = Atoms[Name].get();
    return Result;
    }

#if 0
unique_ptr<TSymbol>  TSymbolTable::Find(string Name)
    {
    TSymbolAtom*    Result = nullptr;

    if(Atoms.count(Name))
        Result  = Atoms[Name].get();

    return unique_ptr<TSymbol>(new TSymbol(Result));
    }
#endif

#if 0
void TSymbolTable::AddUniqueSymbol(unique_ptr<TSymbolAtom> Symbol)
    {
    assert(Symbols.count(Symbol->Name()) == 0); // must be unique
    Symbols[Symbol->Name()] = std::move(Symbol);
    }
#endif

TSymbol TSymbolTable::AddAtom(unique_ptr<TSymbolAtom> Atom, vector<TSymbol>& Array)
    {
    auto    Name = Atom->Name();
    // atom by that name should not already exist
    assert(FindAtom(Name) == nullptr);

    TSymbol Result(Atom.get());
    Atoms[Name] = std::move(Atom);
    
    Array.push_back(Result);
    for(auto &Sym : Array)
        {
        if(Sym->IsTerminal())
            printf("hi!\n");
        }

    return Result;
    }

/* NewTerminal() - create new terminal (and add it to symbol table).
 *
 * We create a new symbol atom, store it in the Atoms map.
 * Next, we
 * We create the new symbol, store its unique pointer in the Symbols map,
 * store a raw pointer in our Terminals vector, and return the raw TSymbol pointer.
 * Ownership is retained by the Symbols map, which deletes its TSymbol
 * members when the symbol table is destroyed.
 */
TSymbol TSymbolTable::NewTerminal(TTokenIndex Index)
    {
    DUMPVERBOSE("NewTerminal(TokenIndex=%d)\n", (int)Index);
    auto Terminal   = unique_ptr<TSymbolAtom>{make_unique<TSymbolTerminal>(Index)};
    auto Result     = AddAtom(std::move(Terminal), Terminals);

    return Result;
    }

TSymbol TSymbolTable::NewNonterminal(TTokenIndex Index)
    {
    DUMPVERBOSE("NewNonterminal(TokenIndex=%d)\n", (int)Index);
    auto NonTerminal    = unique_ptr<TSymbolAtom>{make_unique<TSymbolNonTerminal>(Index)};
    auto Result         = AddAtom(std::move(NonTerminal), NonTerminals);

    return Result;
    }

TSymbol TSymbolTable::NewRule(string Name)
    {
    auto Rule   = unique_ptr<TSymbolAtom>{make_unique<TSymbolRule>(Name)};
    auto Result = AddAtom(std::move(Rule), Rules);

    return Result;
    }


#if 0


/* constants
************************************************************************/    

#define HASH_TABLE_SIZE     (199)       /* use prime number so modulo is decent hash */


/* type definitions
************************************************************************/    

struct  TSymbol;
struct  TSymAtom;

typedef struct  TSymAtom        /* underlying atomic object             */
    {
    TToken      Name;           /* convenience: token of first instance */
    int         BitFlags;
    SYMLIST     Instances;      /* list of all instances of this atom   */
    }           TSymAtom;

typedef struct  TSymbol
    {
    TToken      Name;           /* Token associated with this instance  */
    TSymAtom*   Atom;
    }           TSymbol;

enum
    {
    SYM_TERMINAL                = 0x0001,
    SYM_LEFTRECURSIVE           = 0x0002,
    SYM_FIRSTFOLLOWCLASH        = 0x0004,
    SYM_NULLABLE                = 0x0008,
    SYM_USEDINRULE              = 0x0010,
    SYM_SIMPLE                  = 0x0020,   /* symbol derives only terminals    */
    SYM_EMPTY                   = 0x0040,   /* symbol derives nothing           */
    };

/* file scope variables
************************************************************************/
static  SYMLIST HashTable[HASH_TABLE_SIZE];


/* internal function declarations
************************************************************************/    

static SYMBOL       SymbolNew(TToken Token, int Terminal);
static TSymAtom*    SymAtomFind(TToken Token, int Create);
static void         SymAtomBitSet(TSymAtom* Atom, int Bit);

/* external function definitions
************************************************************************/    

/* SymbolNewNonTerm() - create a new non-terminal data structure.
 *
 * We assume someone else has already verified that no symbol
 * with this name already exists.
 ***********************************************************************/
SYMBOL  SymbolNewNonTerm(TToken Token)
    {
    SYMBOL  Result;

    Result              = SymbolNew(Token, FALSE);
    Globals.NonTerms    = SymListAdd(Globals.NonTerms, Result);

    return Result;
    }

/* SymbolNewTerm() - create a new terminal data structure.
 *
 * We assume someone else has already verified that no symbol
 * with this name already exists.
 ***********************************************************************/
SYMBOL  SymbolNewTerm(TToken Token)
    {
    TSymbol*    Result;

    if(SymbolFind(Token, FALSE))
        ErrorExit(ERROR_TERM_ALREADY_DEFINED, "Terminal already defined: '%.*s'\n",
              Token.TextLen, Token.Text);

    Result  = SymbolAdd(Token, SYM_TERMINAL);
    assert(Result != NULL);


    Globals.Terminals   = SymbolListAdd(Globals.Terminals, Result);

    /* Bit of a hack, but we'll set ->First here.
     * the FIRST() set of a terminal is just the terminal
     */
    Result->First   = SymbolListAdd(Result->First, Result);

    return (SYMBOL)Result;
    }

TSymbol*    SymbolNewTerm(TToken Token)
    {
    TSymbol*    Result;

    if(SymbolFind(Token))
        ErrorExit(ERROR_TERM_ALREADY_DEFINED, "Terminal already defined: '%.*s'\n",
              Token.TextLen, Token.Text);

    Result  = SymbolAdd(Token, SYM_TERMINAL);
    assert(Result != NULL);


    Globals.Terminals   = SymbolListAdd(Globals.Terminals, Result);

    /* Bit of a hack, but we'll set ->First here.
     * the FIRST() set of a terminal is just the terminal
     */
    Result->First   = SymbolListAdd(Result->First, Result);

    return Result;
    }


/* SymbolNew() - return a new symbol instance.
 */
static SYMBOL       SymbolNew(TToken Token, int Terminal)
    {
    TSymbol*    Symbol = NEW(TSymbol);

    assert(Symbol != NULL);
    Symbol->Name    = Token;
    Symbol->Atom    = SymAtomFind(Token, TRUE);
    /* if symbol already existed */
    if(SymListCount(Symbol->Atom->Instances) == 0)
        assert(!Terminal == !SymAtomBitGet(Symbol->Atom, SYM_TERMINAL));
    else if(Terminal)
        SymAtomBitSet(Symbol->Atom, SYM_TERMINAL);

    /* add this new instance to atom's list of instances */
    Symbol->Atom->Instances = SymListPush(Symbol->Atom->Instances, Symbol);

    return (SYMBOL)Symbol;
    }

/* internal functions
************************************************************************/    

/* Hash() - hash a name (from a TToken).
 *
 * A simple XOR hash plus module the prime table size.
 ************************************************************/
static size_t  Hash(const char* Name, size_t Len)
    {
    size_t  Result  = Len;

    while(Len-- > 0)
        Result  = (Result << 5) ^ *Name++;

    return Result % HASH_TABLE_SIZE;
    }


/* SymAtomRef() - create/locate a symbol atom.
 *
 * A TSymAtom contains the symbol attributes that are the same
 * across all instances of that symbol.
 ***************************************************************/
static TSymAtom*    SymAtomFind(TToken Token, int Create)
    {
    size_t      Bucket  = Hash(Token.Text, Token.TextLen);
    TSymAtom*   Atom = NULL;
    SYMLIST     SymList;
    TSymbol*    Instance;

    SymList     = HashTable[Bucket];
    Instance    = (TSymbol*)SymbolListFind(SymList, Token);

    if(Instance)                    /* if atom already exists */
        Atom    = Instance->Atom;
    else if(Create)                 /* if caller wants us to create it */
        {
        Atom    = NEW(TSymAtom);
        assert(Atom);
        Atom->Name  = Token;
        }
    
    return Atom;
    }

static void         SymAtomBitSet(TSymAtom* Atom, int Bit)
    {
    Atom->BitFlags  |= Bit;
    }
#endif
